package com.zg.common.log;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.zg.common.core.C;
import com.zg.common.core.R;
import com.zg.common.core.dao.system.SysLog;
import com.zg.common.core.dao.system.SysLogDao;
import com.zg.common.web.Req;
import com.zg.common.core.User;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.zg.common.core.util.JsonUtil;
import com.zg.common.core.util.TokenUtil;
import lombok.AllArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.sql.Timestamp;

/**
 * 记录请求日志切面
 *
 * @author Zxb
 */
@Aspect
@Component
@AllArgsConstructor
public class LogRequestAspect {

    private final ThreadPoolTaskExecutor taskExecutor;
    private final SysLogDao sysLogDao;

    @Pointcut("@annotation(com.zg.common.log.LogRequest)")
    public void logRequestPointCut() {
    }

    public static String getParam(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] paramNames = signature.getParameterNames();
        Boolean hasParamNames = paramNames != null && paramNames.length > 0;
        ObjectNode on = JsonUtil.on();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            String paramName = hasParamNames ? paramNames[i] : ("参数" + i);
            if (arg instanceof MultipartFile) {
                String name = ((MultipartFile) arg).getOriginalFilename();
                Long size = ((MultipartFile) arg).getSize();
                on.put(paramName, name + "[" + size + "]");
            } else if (arg instanceof HttpServletRequest) {
                on.put(paramName, "HttpServletRequest");
            } else if (arg instanceof HttpServletResponse) {
                on.put(paramName, "HttpServletResponse");
            } else {
                if (arg == null) {
                    on.set(paramName, null);
                } else {
                    try {
                        String jsonStr = JsonUtil.OM.writeValueAsString(arg);
                        on.put(paramName, jsonStr);
                    } catch (Exception e) {
                        on.set(paramName, null);
                    }
                }
            }
        }
        return on.toString();
    }

    private SysLog initLog(JoinPoint joinPoint, LogRequest logRequest) {
        SysLog sysLog = new SysLog();
        String param = getParam(joinPoint);
        String token = Req.getHeader(C.TOKEN_KEY);
        User u = TokenUtil.validateToken(token);
        String username = u == null ? null : u.getUsername();

        sysLog.setOpName(logRequest.opName());
        sysLog.setOpModule(logRequest.opModule());
        sysLog.setOpType(logRequest.opType().name());
        sysLog.setRequestIp(Req.getIp());
        sysLog.setBrowser(Req.getBrowser());
        sysLog.setOs(Req.getOs());
        sysLog.setHttpMethod(Req.getMethod());
        sysLog.setClassName(joinPoint.getTarget().getClass().getName());
        sysLog.setMethodName(joinPoint.getSignature().getName());
        sysLog.setToken(token);
        sysLog.setUsername(username);
        sysLog.setRequestParam(param);
        sysLog.setRequestUri(Req.getURI());
        StringBuffer sb = new StringBuffer();
        sb.append(sysLog.getOpModule())
                .append(sysLog.getOpName())
                .append(sysLog.getHttpMethod())
                .append(sysLog.getOpType())
                .append(sysLog.getUsername())
                .append(sysLog.getRequestUri());
        sysLog.setSearchStr(sb.toString());
        return sysLog;
    }

    @Before("logRequestPointCut()")
    public void doBefore() {
        long startTime = System.currentTimeMillis();
        RequestContextHolder.set(startTime);
    }

    @AfterReturning(pointcut = "logRequestPointCut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) throws JsonProcessingException {
        long startTime = RequestContextHolder.get();

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        LogRequest logRequest = method.getAnnotation(LogRequest.class);

        SysLog sysLog = initLog(joinPoint, logRequest);

        sysLog.setRequestTime(new Timestamp(startTime));
        long spend = System.currentTimeMillis() - startTime;
        sysLog.setSpend(spend);
        sysLog.setState(OpResultEnum.SUCCESS.name());
        if (result instanceof R) {
            if (!((R<?>) result).isOk()) {
                sysLog.setState(OpResultEnum.FAIL.name());
                sysLog.setExpInfo(((R<?>) result).getMessage());
            }
        }
        String responseJson = JsonUtil.OM.writeValueAsString(result).replaceAll("'", "\\\\'");
        sysLog.setRequestResult(responseJson);
        if (!sysLog.getOpType().equals(OpTypeEnum.QUERY.name())) {
            taskExecutor.execute(() -> sysLogDao.save(sysLog));
        }
    }

    @AfterThrowing(pointcut = "logRequestPointCut()", throwing = "exp")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable exp) {
        long startTime = RequestContextHolder.get();

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        LogRequest logRequest = method.getAnnotation(LogRequest.class);

        SysLog sysLog = initLog(joinPoint, logRequest);

        sysLog.setRequestTime(new Timestamp(startTime));
        long spend = System.currentTimeMillis() - startTime;
        sysLog.setSpend(spend);
        sysLog.setState(OpResultEnum.FAIL.name());
        sysLog.setExpInfo(ExceptionUtil.getRootCauseMessage(exp));
        if (!sysLog.getOpType().equals(OpTypeEnum.QUERY.name())) {
            taskExecutor.execute(() -> sysLogDao.save(sysLog));
        }
    }

}
